/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.server.embedded;

import java.awt.Desktop;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.security.ProtectionDomain;
import javax.swing.SwingWorker;
import offset.nodes.server.embedded.view.InstallerDialog;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;

import org.mortbay.jetty.Connector;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.handler.HandlerList;
import org.mortbay.jetty.nio.SelectChannelConnector;
import org.mortbay.jetty.webapp.WebAppContext;
import org.mortbay.thread.QueuedThreadPool;

public class Launcher {

    public static final String CONTEXT_PATH = "/nodes";
    public static final String WORK_DIR = "work";
    public static final String REPOSITORY_DIR = "nodes";
    public static final String REPOSITORY_HOME = "NODES_REPOSITORY_HOME";
    public static final String NODES_HOME = "NODES_HOME";
    public static final String APPLICATION_LOG_FILE = "nodes-log4j.properties";
    NodesConfig config = new NodesConfig();
    private static Logger startup = Logger.getLogger("offset.nodes.startup");
    InstallerDialog installer;

    public static void main(String[] args) throws Exception {
        Launcher server = new Launcher();

        if (args.length != 1)
            server.start();
        else if ("stop".equals(args[0]))
            server.stop();
        else if ("start".equals(args[0]))
            server.start();
        else
            server.usage();
    }

    public Launcher() {
    }

    private void start() {
        // Start a Jetty server with some sensible(?) defaults
        try {
            config.load();

            boolean serverRunning = isServerRunning();
            if (serverRunning)
                installer = new InstallerDialog(config, new ServerShutdown(), serverRunning);
            else
                installer = new InstallerDialog(config, new ServerStartup(), serverRunning);
            initLogging();

            installer.setVisible(true);
        } catch (Throwable e) {
            startup.error("Exception:", e);
        }
    }

    private boolean isServerRunning() throws MalformedURLException, IOException {
        URL server = new URL("http", "localhost", config.getPort(), "");
        HttpURLConnection connection = (HttpURLConnection) server.openConnection();
        try {
            connection.connect();
        } catch (Exception e) {
            return false;
        }
        
        return true;
    }

    private void install() throws IOException, URISyntaxException {
        config = installer.getConfig();
        copyFiles(new File(new URL(getWar()).toURI()));
    }

    private void startServer() {

        try {
            if (!installer.getConfig().isInstalled())
                install();

            System.setProperty(REPOSITORY_HOME, config.getRepository().getParentFile().getPath());

            Server srv = new Server();
            srv.setStopAtShutdown(true);

            // Allow 5 seconds to complete.
            // Adjust this to fit with your own webapp needs.
            // Remove this if you wish to shut down immediately (i.e. kill <pid>
            // or Ctrl+C).
            srv.setGracefulShutdown(5000);

            // Increase thread pool
            QueuedThreadPool threadPool = new QueuedThreadPool();
            threadPool.setMaxThreads(100);
            srv.setThreadPool(threadPool);

            // Ensure using the non-blocking connector (NIO)
            Connector connector = new SelectChannelConnector();
            connector.setPort(config.getPort());
            connector.setMaxIdleTime(30000);
            srv.setConnectors(new Connector[]{connector});

            // Get the war-file
            ProtectionDomain protectionDomain = Launcher.class.getProtectionDomain();
            String war = getWar();

            startup.info("Logging to " + config.getInstallDir() + File.separator + "nodes.log");
            startup.info(
                    "Starting WAR = " + war);

            // Add the warFile (this jar)
            WebAppContext context = new WebAppContext(war, CONTEXT_PATH);
            context.setServer(srv);
            setTempDirectory(context, config.getInstallDir().getPath());

            // Add the handlers
            HandlerList handlers = new HandlerList();
            handlers.addHandler(context);
            handlers.addHandler(
                    new ShutdownHandler(srv, context));
            srv.setHandler(handlers);

            long start = System.currentTimeMillis();

            srv.start();

            long end = System.currentTimeMillis();

            startup.info(
                    "Started embedded server in" + (end - start) / 1000 + " seconds");

            Desktop desktop = Desktop.getDesktop();

            if (config.isShowBrowser() && desktop.isSupported(java.awt.Desktop.Action.BROWSE))
                desktop.browse(new URI("http://localhost:8080/nodes/registry/Project"));

            installer.close();

            srv.join();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void stop() throws MalformedURLException, IOException {
        startup.info("Attempting to shutdown server ...");
        URL shutdown = new URL(ShutdownHandler.shutdown(config.getPort()));
        HttpURLConnection connection = (HttpURLConnection) shutdown.openConnection();
        connection.setRequestMethod("POST");
        connection.getResponseCode();
        
        installer.close();
    }

    private void usage() {
        System.out.println("Usage: java -jar <file.jar> [start|stop|\n\t"
                + "start    Start the server (default)\n\t"
                + "stop     Stop the server gracefully\n\t");
        System.exit(-1);
    }

    private void setTempDirectory(WebAppContext context, String installDir)
            throws IOException {
        File workDir = new File(installDir, WORK_DIR);

        if (workDir.exists())
            context.setExtractWAR(false);
        context.setTempDirectory(workDir);
    }

    /**
     * Return the URL of the WAR file
     *
     * @return the URL as a string
     * @throws MalformedURLException
     */
    protected String getWar() throws MalformedURLException {
        return this.getClass().getProtectionDomain().getCodeSource().getLocation().toExternalForm();
    }

    /**
     * Copy application files to installation directory
     *
     * @param warFile
     * @throws IOException
     * @throws URISyntaxException
     */
    protected void copyFiles(File warFile) throws IOException, URISyntaxException {
        // war
        File targetWarFile = new File(config.getInstallDir() + File.separator + warFile.getName());

        if (!targetWarFile.equals(warFile))
            FileUtils.copyFile(warFile, new File(config.getInstallDir() + File.separator + warFile.getName()));

        // config
        config.store();

        // logging configuration
        InputStream logConfig = this.getClass().getResourceAsStream(APPLICATION_LOG_FILE);

        if (logConfig != null) {
            File targetLogConfigurationFile = new File(config.getInstallDir() + File.separator + APPLICATION_LOG_FILE);
            copyStreamToFile(
                    logConfig, targetLogConfigurationFile);
        }
    }

    /**
     * Copy the stream to a file
     * 
     * @param in
     * @param outFile
     * @throws IOException
     */
    protected void copyStreamToFile(InputStream in, File outFile) throws IOException {
        OutputStream out = new FileOutputStream(outFile);

        byte[] buf = new byte[8192];

        try {
            int count = 0;

            while ((count = in.read(buf)) > 0) {
                out.write(buf, 0, count);
            }
        } finally {
            out.close();
        }
    }

    /**
     * Init the logging. Either from a logging file in the installation directory or from the config file in the jar.
     */
    protected void initLogging() {
        // set property for logging configuration
        System.setProperty(NODES_HOME, config.getInstallDir().getPath());

        File logConfigFile = new File(config.getInstallDir() + File.separator + APPLICATION_LOG_FILE);

        if (logConfigFile.exists())
            PropertyConfigurator.configure(logConfigFile.getPath());
        else {
            URL in = this.getClass().getResource(APPLICATION_LOG_FILE);

            if (in != null)
                PropertyConfigurator.configure(in);
        }

        // init startup dialog logging
        Logger jettyLogger = Logger.getLogger("org.mortbay");
        startup.addAppender(new TextAreaAppender(installer.getProgressMessages()));
        jettyLogger.addAppender(new TextAreaAppender(installer.getProgressMessages()));
    }

    /**
     * Start the server and update progress panel
     */
    class ServerStartup extends SwingWorker<Void, Void> {

        @Override
        protected Void doInBackground() throws Exception {
            startServer();
            return null;
        }
    }

    /**
     * Start the server and update progress panel
     */
    class ServerShutdown extends SwingWorker<Void, Void> {

        @Override
        protected Void doInBackground() throws Exception {
            stop();
            return null;
        }
    }
}
